﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2022-2022 LiLong                                              **
**  This file is part of BluetoothCpp.                                          **
**                                                                              **
**  BluetoothCpp is free software: you can redistribute it and/or modify        **
**  it under the terms of the GNU Lesser General Public License as published by **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  BluetoothCpp is distributed in the hope that it will be useful,             **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU Lesser General Public License for more details.                         **
**                                                                              **
**  You should have received a copy of the GNU Lesser General Public License    **
**  along with BluetoothCpp.  If not, see <https://www.gnu.org/licenses/>.      **
**********************************************************************************/
#include "ServerWin.h"
#include "../Socket.h"

#ifdef WIN32

#include <WinSock2.h>
#include <ws2bth.h>

#include "ErrorHandleWin.h"

namespace BTH
{
struct ServerWin::Impl
{
    SOCKET socket { INVALID_SOCKET };
    SOCKADDR_BTH addr { .addressFamily = AF_BTH };
    WSAQUERYSETW wsaQuerySet { 0 };
    LPCSADDR_INFO lpCSAddrInfo;
    std::wstring inst;
    std::wstring comment { L"Example Service instance registered in the directory service through RnR" };
    GUID guid;
};

ServerWin::ServerWin() : m_impl(std::make_unique<Impl>()) {}

ServerWin::~ServerWin()
{
    if (m_impl->lpCSAddrInfo)
    {
        HeapFree(GetProcessHeap(), 0, m_impl->lpCSAddrInfo);
    }
}

void ServerWin::listen(const Uuid& uuid /*= {}*/)
{
    m_impl->socket              = ::socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
    m_impl->addr.port           = BT_PORT_ANY;
    m_impl->addr.serviceClassId = uuid;
    m_impl->guid                = uuid;
    if (::bind(m_impl->socket, reinterpret_cast<sockaddr*>(&m_impl->addr), sizeof(m_impl->addr)) == SOCKET_ERROR)
        throwError(WSAGetLastError());

    int len = sizeof(SOCKADDR_BTH);
    if (::getsockname(m_impl->socket, (struct sockaddr*)&m_impl->addr, &len) == SOCKET_ERROR)
        throwError(WSAGetLastError());
    m_impl->addr.serviceClassId = uuid;
    DWORD dwLenComputerName     = MAX_COMPUTERNAME_LENGTH + 1;
    m_impl->inst.resize(dwLenComputerName);
    GetComputerNameW(m_impl->inst.data(), &dwLenComputerName);
    m_impl->inst.resize(dwLenComputerName);
    m_impl->wsaQuerySet.dwSize                  = sizeof(WSAQUERYSETW);
    m_impl->wsaQuerySet.lpServiceClassId        = &m_impl->guid;
    m_impl->wsaQuerySet.lpszServiceInstanceName = m_impl->inst.data();
    m_impl->wsaQuerySet.lpszComment             = m_impl->comment.data();
    m_impl->wsaQuerySet.dwNameSpace             = NS_BTH;
    m_impl->wsaQuerySet.dwNumberOfCsAddrs       = 1; // Must be 1.
    m_impl->lpCSAddrInfo                        = (LPCSADDR_INFO)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(CSADDR_INFO));
    if (!m_impl->lpCSAddrInfo)
        throw std::exception("Unable to allocate memory for CSADDR_INFO");

    m_impl->lpCSAddrInfo[0].LocalAddr.iSockaddrLength  = sizeof(SOCKADDR_BTH);
    m_impl->lpCSAddrInfo[0].LocalAddr.lpSockaddr       = (LPSOCKADDR)&m_impl->addr;
    m_impl->lpCSAddrInfo[0].RemoteAddr.iSockaddrLength = sizeof(SOCKADDR_BTH);
    m_impl->lpCSAddrInfo[0].RemoteAddr.lpSockaddr      = (LPSOCKADDR)&m_impl->addr;
    m_impl->lpCSAddrInfo[0].iSocketType                = SOCK_STREAM;
    m_impl->lpCSAddrInfo[0].iProtocol                  = BTHPROTO_RFCOMM;

    m_impl->wsaQuerySet.lpcsaBuffer = m_impl->lpCSAddrInfo; // Req'd.

    publicSDP();

    if (::listen(m_impl->socket, SOMAXCONN) == SOCKET_ERROR)
        throwError(WSAGetLastError());
}

Handle ServerWin::accept()
{
    SOCKADDR_BTH addr;
    auto client = ::accept(m_impl->socket, reinterpret_cast<sockaddr*>(&addr), nullptr);
    if (client == INVALID_SOCKET)
        throwError(WSAGetLastError());
    return reinterpret_cast<Handle>(client);
}

void ServerWin::close() { ::closesocket(m_impl->socket); }

BTH::Handle ServerWin::handle() const { return reinterpret_cast<Handle>(m_impl->socket); }

void ServerWin::publicSDP()
{
    if (WSASetServiceW(&m_impl->wsaQuerySet, RNRSERVICE_REGISTER, 0) == SOCKET_ERROR)
        throwError(WSAGetLastError());
}

} // namespace BTH
#endif
